C demo of merging two files into a new sorted file using a linked-list ... by inserting, in sorted order, into a new (growing) list.

=====================================================

/*
    C demo of merging two files into a new sorted file ... using a
    linked-list ... by inserting, in sorted order, into the (growing) list.
    
    Note: using fscanf(fp, "%s", buffer) to read each WORD on the file line...
    EXCEPT FOR the last chunk on the line ... That is obtained by ...
    pS->first = getString( fp ); // reads the WHOLE rest of the line ... //
    
    http://developers-heaven.net/forum/index.php/topic,46.0.html
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* strlen, strcpy */

#define fName1 "info1.txt"
#define fName2 "info2.txt"
#define fName3 "merge.txt"
#define WORD_CHUNK 32 /* adjust to your data's size to minimize realloc calls */

/*
info1.txt

09876 year1 Smith Catee Rose
08765 year2 Clinton Hilary Claire
07654 year3 Barrack Richard Kenneth

info2.txt

08967 year1 Edison Bill
06789 year2 Bell Robert Ed

merge.txt

07654 year3 Barrack Richard Kenneth
06789 year2 Bell Robert Ed
08765 year2 Clinton Hilary Claire
08967 year1 Edison Bell
09876 year1 Smith Catee Rose
*/

typedef struct student
{
    char* id;
    char* year;
    char* last;
    char* first;
    struct student* next;
} Student;

typedef Student* pStudent;

/* these 2 global var's used to simplify function calls ... */
pStudent phead = NULL; /* Note: need a NULL value to start inserts ... */
int numStudents = 0;

void insert( pStudent pS );
int readFile( char* name );
int writeFile( char* name );
int studentCmp( pStudent pS1, pStudent pS2 );
char* newCopy( char* str );
char* getString( FILE* fp );
void view( pStudent pS );
void showAll();
void delAll();
void flushStdin();
void myAssert( int condition, char message[] );


int main() /* start of main ***************************************************/
{

    int num = readFile( fName1 );
    if( num )
        numStudents += num;
    else
    {
        printf("Error in reading %s. Press 'Enter' to exit ...", fName1 );
        flushStdin();
    }

    num = readFile( fName2 );
    if( num )
        numStudents += num;
    else
    {
        printf("Error in reading %s. Press 'Enter' to exit ...", fName2 );
        flushStdin();
    }

    showAll();

    num = writeFile( fName3 );
    if( num == numStudents )
        printf
        (
            "All %d students were successfully sorted and merged in file %s.",
            num, fName3
        );
    else
        printf
        (
            "Error: writing to file %s  Note: wrote %d vs. numStudents of %d",
            fName3, num, numStudents
        );

    printf("\n\nPress 'Enter' to continue ... ");
    flushStdin();

    delAll();
    /* if using Windows oS ... */
    system( "notepad merge.txt" );

    return 0;
} /* end of main **************************************************************/



void flushStdin()
{
    while( getchar()!='\n' ) ;
}

char* newCopy( char* str )
{
    char* nCopy = (char*) malloc( strlen(str)+1 );
    myAssert( (nCopy != NULL), "Error: malloc failed (0) " );
    strcpy( nCopy, str );
    return nCopy;
}

char* getString( FILE* fp )
{
    int c, i= 0, chunk = WORD_CHUNK;
    char* tmp;
    char* buffer = (char*) calloc( chunk, 1 );
    myAssert( (buffer != NULL), "Error: calloc failed (1) " );
    while( (c=fgetc(fp))!=EOF && c!='\n' ) /*eats up WHOLE line including '\n'*/
    {
        if( i == chunk - 2 )
        {
            chunk += WORD_CHUNK;
            tmp = (char*) realloc( buffer, chunk );
            if( tmp == NULL )
            {
                free( buffer );
                myAssert( 0, "Error: realloc failed (2) " );
            }
        }
        buffer[i++] = c;
    }
    buffer[i] = 0;
    tmp = (char*) realloc( buffer, i+1  );
    if( tmp == NULL )
    {
        free( buffer );
        myAssert( 0, "Error: realloc failed (3) " );
    }
    return buffer;
}

/* insert in list with last & first names in proper order */
void insert( pStudent pS )
{
    pStudent q = phead; /* Get a working copy of phead in q */
    
    /* Firstly, we handle the case where 'this' should be the first element. */
    if(phead == NULL || studentCmp(pS, phead) < 0)
    {
        /* So ... it now becomes the first element ... */
        pS->next = phead; /* old phead becomes 2nd in list ... */
        phead = pS; /* and ... this pS ... becomes the head of the list */
    }
    else /* If here ... search the linked list for the right location ... */
    {
        /* Start comparing with element AFTER 'q' ... i.e. the 'next in' ... */
        while(q->next != NULL && studentCmp(q->next, pS) < 0)
        {
            q = q->next; /* Traverse the list ... */
        }
        /*
            Ok, insert after 'q' by relinking the pointers (similar to above)
            ( Includes inserting at end position ... where q->next == NULL )
        */
        pS->next = q->next; /* Inserted 'pS' is linked to 'upstream element' */
        q->next = pS;  /* Now 'q' is updated to link to the new 'pS' element */
    }
}

void view( pStudent pS )
{
    printf
    (
        "ID: %s  "
        "Year: %s  "
        "Name: %s,%s", /* Note: 2nd string already contains a leading space */
        pS->id, pS->year, pS->last, pS->first
    );
}

void showAll()
{
    pStudent p = phead;     /* int c; */
    if( phead == NULL )
    {
        puts("\nNo records in memory at present.") ;
        return;
    }

    /* If reach here ... */
    while( p != NULL )
    {
        view( p );
        putchar( '\n' );
        p = p->next;
    }
    fflush( stdout );
}

int writeFile(char *name)
{
    FILE* fp;
    int count = 0; /* to track the records actually written to the file */
    pStudent p = phead;
    if( p == NULL )
    {
        puts("\nNo records available ... so NO records written to file.") ;
        return 0;
    }
    
    fp = fopen( name, "w" );
    if( fp == NULL )
    {
        printf("\nError opening file %s.  Press 'Enter' to continue ... ", name);
        flushStdin();
        return 0;
    }

    while( p != NULL )
    {
        fprintf
        (
            fp,
            "%s "
            "%s "
            "%s," /*no space here ... since a leading space is in next string*/
            "%s\n",
            p->id, p->year, p->last, p->first
        );

        ++count;
        p = p->next;
    }
    fclose( fp );
    return count; /* Number of records written. */
}

int readFile( char* name )
{
    int count = 0;
    pStudent pS;
    char buffer[WORD_CHUNK+1];
    FILE* fp = fopen( name, "r" );
    if( fp  == NULL )
    {
        printf
        (
            "\nError opening file %s.\n"
            "Perhaps it hasn't been created yet?\n"
            "Press 'Enter' to continue ... ",
            name
        );
        flushStdin();
        return 0;
    }

    /* If the program reaches here ... */
    while(  fscanf(fp, "%s", buffer) != EOF  )
    {
        pS = (pStudent) malloc( sizeof(Student) );
        myAssert( (pS != NULL), "Error: malloc failed (4) " );
        
        pS->id = newCopy( buffer );

        fscanf( fp, "%s", buffer );
        pS->year = newCopy( buffer );

        fscanf( fp, "%s", buffer );
        pS->last = newCopy( buffer );

        pS->first = getString( fp ); /* reads the WHOLE rest of the line ... */

        insert( pS );
        ++count;
    }
    fclose( fp );

    printf("%d records were read into memory from the file %s.\n", count, name);

    return count; /* Total number of student records found in the file. */
}

/* A function to compare two student records to permit sorting ... */
int studentCmp( pStudent pS1, pStudent pS2 )
{
    int compare = strcmp(pS1->last, pS2->last); /* compare last_names */
    if ( compare == 0 ) /* if last_names same ... use first_names */
        return strcmp(pS1->first, pS2->first);
    return compare; /* use last_names after all since different */
}

void delAll()
{
    pStudent p = phead; /* set p to this initial value to start loop */
    pStudent pNext;     /* to hold the pointer to the next record */

    while( p != NULL )
    {
        pNext = p->next; /* get pointer to the next record */

        free( p->first );
        free( p->last );
        free( p->year );
        free( p->id );
        
        free( p );

        p = pNext; /* update p ... */
    }

    /* update globals ...  */
    phead = NULL;
    numStudents = 0;
}

void myAssert( int condition, char message[] )
{
    if( !condition )
    {
        fputs( message, stderr );
        getchar();
        exit(1);
    }
}

